package com.ahdms.es.engine;

import com.ahdms.es.bean.CertInfo;
import com.ahdms.es.bean.SesSignInfo;
import com.ahdms.es.bean.enums.ApiCode;
import com.ahdms.es.bean.enums.VerifyCertCode;
import com.ahdms.es.config.PropertiesConfig;
import com.ahdms.es.exceptions.*;
import com.ahdms.es.gm.constant.IKIObjectIdentifiers;
import com.ahdms.es.gm.v1.SES_Signature;
import com.ahdms.es.result.VerifyResult;
import com.ahdms.es.util.CalculateHashUtils;
import com.ahdms.es.util.LdapUtils;
import com.ahdms.es.util.SM2Utils;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.Certificate;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

/**
 * @author qinxiang
 * @date 2021-04-09 11:16
 */
public abstract class SesSignatureVerifyEngine {

    protected Date revokeDate;

    public static SesSignatureVerifyEngine getInstance(byte[] sesSignatureData) throws VerifySesSignException {
        ASN1Sequence seq = ASN1Sequence.getInstance(sesSignatureData);
        int size = seq.size();
        if (size >= 4 && size <= 5) {
            return SesSignatureGBVerifyEngine.getInstance(sesSignatureData);
        } else {
            if (size != 2) {
                throw new VerifySesSignException(ApiCode.SESIGN_VERSION_FAIL);
            }
            return SesSignatureGMVerifyEngine.getInstance(sesSignatureData);
        }
    }

    public static VerifyResult<SesSignInfo> verifySesSignature(byte[] sesSignatureData, byte[] srcData) {
        try {
            return getInstance(sesSignatureData).verify(srcData);
        } catch (VerifySesSignException e) {
            return VerifyResult.error(e.getAlertAble());
        }
    }

    public static VerifyResult<SesSignInfo> verifySesSignature(SES_Signature sesSignature, byte[] srcData) {
        return new SesSignatureGMVerifyEngine(sesSignature).verify(srcData);
    }

    public static VerifyResult<SesSignInfo> verifySesSignature(com.ahdms.es.gm.v4.SES_Signature sesSignature, byte[] srcData) {
        return new SesSignatureGBVerifyEngine(sesSignature).verify(srcData);
    }

    public VerifyResult<SesSignInfo> verify(byte[] srcData) {
        try {
            verifySignData();
            verifySignCert();
            validateSignDate();
            verifySrcFileHash(srcData);
            verifySeSeal();
            return VerifyResult.success(getSesSignInfo());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (VerifyException e) {
            e.printStackTrace();
            return VerifyResult.error(e.getAlertAble());
        } catch (ApiException e) {
            e.printStackTrace();
            return VerifyResult.error(e.getMessage());
        }
        return VerifyResult.error(ApiCode.SESIGN_FORMART_FAIL);
    }

    /**
     * 2.验证电子签章签名值是否正确
     */
    public abstract void verifySignData() throws IOException,VerifySesSignException;

    /**
     * 3.验证签章人数字证书有效性
     *
     * @return
     */
    public void verifySignCert() throws VerifySesSignException {
        X509CertVerifyEngine certVerifyEngine = X509CertVerifyEngine.getInstance(getSignCert());
        try {
            certVerifyEngine.verifyCertTrustChain(getSignCert());
            boolean b = certVerifyEngine.verifyCertKeyUsage(getSignCert());
            if(!b){
                throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_SignCert_Fail);
            }
            isRevoke();
        } catch (VerifyCertException e) {
            e.printStackTrace();
            throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_SignCert_Fail);
        }

//        VerifyResult<CertInfo> verifyCertResult = X509CertVerifyEngine.verifyX509Cert(getSignCert());
//
//        if(!verifyCertResult.isResult()){
//            if(VerifyCertCode.Verify_Revoke_Fail.getCode() == verifyCertResult.getCode()){
//                isRevoke();
//                return;
//            }
//            throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_SignCert_Fail);
//        }
    }

    /**
     * 4.验证签章的时间有效性
     */
    public void validateSignDate() throws VerifySesSignException {
        //根据签章人数字证书有效期和电子签章中的时间信息进行比对，判断签章的时间有效性
        Date startDate = getSignCert().getStartDate().getDate();
        Date endDate = getSignCert().getEndDate().getDate();
        //4.1 如果签章时间处于签章人数字证书有效期内，并且证书有效，则需要继续进一步验证
        if (getSignDate().after(startDate) && getSignDate().before(endDate)) {
            if (getRevokeDate() != null && getRevokeDate().before(getSignDate())) {
                //4.3 如果签章时间处于签章人数字证书有效期内，但证书在签章之前已被吊销，则签章无效
                throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_DATE_FAIL);
            }
        } else {
            //4.2 如果签章时间不在签章人数字证书有效期内，则签章无效
            throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_SignDate_CertDate_Fail);
        }
    }

    /**
     * 验证原文杂凑值
     * @param srcData
     */
    public void verifySrcFileHash(byte[] srcData) throws VerifySesSignException {
        // TODO dms电子签章系统电子签章数据的杂凑值是做了Base64之后的值
        if (!Arrays.equals(CalculateHashUtils.sm3Digest(srcData), getSesDataHash())) {
            throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_DATAHASH_FAIL);
        }
    }

    public abstract void verifySeSeal() throws InvalidSealFormatException, VerifySesSignException;

    protected void verifySignData(Certificate x509Cert, String algorithm, byte[] sourceData, byte[] signData) throws VerifySesSignException {
        if (Objects.equals(IKIObjectIdentifiers.sm2_with_sm3.getId(), algorithm)) {
            boolean verifySignResult = SM2Utils.verifySign(x509Cert, sourceData, signData);
            if (!verifySignResult) {
                throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_SIGN_FAIL);
            }
        } else {
            throw new VerifySesSignException(ApiCode.SESIGN_SIGN_Algorithm_FAIL);
        }
    }

    public void validateSignDate(Date signDate, Date revokeDate) throws VerifySesSignException {
        //根据签章人数字证书有效期和电子签章中的时间信息进行比对，判断签章的时间有效性
        Date startDate = getSignCert().getStartDate().getDate();
        Date endDate = getSignCert().getEndDate().getDate();
        //4.1 如果签章时间处于签章人数字证书有效期内，并且证书有效，则需要继续进一步验证
        if (signDate.after(startDate) && signDate.before(endDate)) {
            if (revokeDate != null && revokeDate.before(signDate)) {
                //4.3 如果签章时间处于签章人数字证书有效期内，但证书在签章之前已被吊销，则签章无效
                throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_DATE_FAIL);
            }
        } else {
            //4.2 如果签章时间不在签章人数字证书有效期内，则签章无效
            throw new VerifySesSignException(ApiCode.SESIGN_VERIFY_SignDate_CertDate_Fail);
        }
    }

    public abstract Certificate getSignCert();

    public void isRevoke(){
        if(!PropertiesConfig.isCrlEnable()){
            return;
        }
        Date date = LdapUtils.queryLdap(getSignCert());
        this.revokeDate = date;
    }

    public Date getRevokeDate(){
        return this.revokeDate;
    }

    public abstract Date getSignDate();

    /**
     * 获取电子签章数据结构中的原文杂凑值
     *
     * @return
     */
    public abstract byte[] getSesDataHash();

    public abstract SesSignInfo getSesSignInfo();
}
